import { gql } from '@urql/core';
import { it, expect, describe } from 'vitest';
import { __initAnd_query as query } from '../operations/query';
import { __initAnd_write as write } from '../operations/write';
import { Store } from '../store/store';
import { MergeMode, simplePagination } from './simplePagination';

describe('as resolver', () => {
  it('works with forward pagination', () => {
    const Pagination = gql`
      query ($skip: Number, $limit: Number) {
        __typename
        persons(skip: $skip, limit: $limit) {
          __typename
          id
          name
        }
      }
    `;

    const store = new Store({
      resolvers: {
        Query: {
          persons: simplePagination(),
        },
      },
    });

    const pageOne = {
      __typename: 'Query',
      persons: [
        { id: 1, name: 'Jovi', __typename: 'Person' },
        { id: 2, name: 'Phil', __typename: 'Person' },
        { id: 3, name: 'Andy', __typename: 'Person' },
      ],
    };

    const pageTwo = {
      __typename: 'Query',
      persons: [
        { id: 4, name: 'Kadi', __typename: 'Person' },
        { id: 5, name: 'Dom', __typename: 'Person' },
        { id: 6, name: 'Sofia', __typename: 'Person' },
      ],
    };

    write(
      store,
      { query: Pagination, variables: { skip: 0, limit: 3 } },
      pageOne
    );
    const pageOneResult = query(store, {
      query: Pagination,
      variables: { skip: 0, limit: 3 },
    });
    expect(pageOneResult.data).toEqual(pageOne);

    write(
      store,
      { query: Pagination, variables: { skip: 3, limit: 3 } },
      pageTwo
    );

    const pageTwoResult = query(store, {
      query: Pagination,
      variables: { skip: 3, limit: 3 },
    });
    expect((pageTwoResult.data as any).persons).toEqual([
      ...pageOne.persons,
      ...pageTwo.persons,
    ]);

    const pageThreeResult = query(store, {
      query: Pagination,
      variables: { skip: 6, limit: 3 },
    });
    expect(pageThreeResult.data).toEqual(null);
  });

  it('works with backwards pagination', () => {
    const Pagination = gql`
      query ($skip: Number, $limit: Number) {
        __typename
        persons(skip: $skip, limit: $limit) {
          __typename
          id
          name
        }
      }
    `;

    const store = new Store({
      resolvers: {
        Query: {
          persons: simplePagination({ mergeMode: 'before' }),
        },
      },
    });

    const pageOne = {
      __typename: 'Query',
      persons: [
        { id: 7, name: 'Jovi', __typename: 'Person' },
        { id: 8, name: 'Phil', __typename: 'Person' },
        { id: 9, name: 'Andy', __typename: 'Person' },
      ],
    };

    const pageTwo = {
      __typename: 'Query',
      persons: [
        { id: 4, name: 'Kadi', __typename: 'Person' },
        { id: 5, name: 'Dom', __typename: 'Person' },
        { id: 6, name: 'Sofia', __typename: 'Person' },
      ],
    };

    write(
      store,
      { query: Pagination, variables: { skip: 0, limit: 3 } },
      pageOne
    );
    const pageOneResult = query(store, {
      query: Pagination,
      variables: { skip: 0, limit: 3 },
    });
    expect(pageOneResult.data).toEqual(pageOne);

    write(
      store,
      { query: Pagination, variables: { skip: 3, limit: 3 } },
      pageTwo
    );

    const pageTwoResult = query(store, {
      query: Pagination,
      variables: { skip: 3, limit: 3 },
    });
    expect((pageTwoResult.data as any).persons).toEqual([
      ...pageTwo.persons,
      ...pageOne.persons,
    ]);

    const pageThreeResult = query(store, {
      query: Pagination,
      variables: { skip: 6, limit: 3 },
    });
    expect(pageThreeResult.data).toEqual(null);
  });

  it('handles duplicates', () => {
    const Pagination = gql`
      query ($skip: Number, $limit: Number) {
        __typename
        persons(skip: $skip, limit: $limit) {
          __typename
          id
          name
        }
      }
    `;

    const store = new Store({
      resolvers: {
        Query: {
          persons: simplePagination(),
        },
      },
    });

    const pageOne = {
      __typename: 'Query',
      persons: [
        { id: 1, name: 'Jovi', __typename: 'Person' },
        { id: 2, name: 'Phil', __typename: 'Person' },
        { id: 3, name: 'Andy', __typename: 'Person' },
      ],
    };

    const pageTwo = {
      __typename: 'Query',
      persons: [
        { id: 3, name: 'Andy', __typename: 'Person' },
        { id: 4, name: 'Kadi', __typename: 'Person' },
        { id: 5, name: 'Dom', __typename: 'Person' },
      ],
    };

    write(
      store,
      { query: Pagination, variables: { skip: 0, limit: 3 } },
      pageOne
    );
    write(
      store,
      { query: Pagination, variables: { skip: 2, limit: 3 } },
      pageTwo
    );

    const result = query(store, {
      query: Pagination,
      variables: { skip: 2, limit: 3 },
    });
    expect(result.data).toEqual({
      __typename: 'Query',
      persons: [...pageOne.persons, pageTwo.persons[1], pageTwo.persons[2]],
    });
  });

  it('should not return previous result when adding a parameter', () => {
    const Pagination = gql`
      query ($skip: Number, $limit: Number, $filter: String) {
        __typename
        persons(skip: $skip, limit: $limit, filter: $filter) {
          __typename
          id
          name
        }
      }
    `;

    const store = new Store({
      resolvers: {
        Query: {
          persons: simplePagination(),
        },
      },
    });

    const pageOne = {
      __typename: 'Query',
      persons: [
        { id: 1, name: 'Jovi', __typename: 'Person' },
        { id: 2, name: 'Phil', __typename: 'Person' },
        { id: 3, name: 'Andy', __typename: 'Person' },
      ],
    };

    const emptyPage = {
      __typename: 'Query',
      persons: [],
    };

    write(
      store,
      { query: Pagination, variables: { skip: 0, limit: 3 } },
      pageOne
    );
    write(
      store,
      { query: Pagination, variables: { skip: 0, limit: 3, filter: 'b' } },
      emptyPage
    );

    const res = query(store, {
      query: Pagination,
      variables: { skip: 0, limit: 3, filter: 'b' },
    });
    expect(res.data).toEqual({ __typename: 'Query', persons: [] });
  });

  it('should preserve the correct order in forward pagination', () => {
    const Pagination = gql`
      query ($skip: Number, $limit: Number) {
        __typename
        persons(skip: $skip, limit: $limit) {
          __typename
          id
          name
        }
      }
    `;

    const store = new Store({
      resolvers: {
        Query: {
          persons: simplePagination({ mergeMode: 'after' }),
        },
      },
    });

    const pageOne = {
      __typename: 'Query',
      persons: [
        { id: 1, name: 'Jovi', __typename: 'Person' },
        { id: 2, name: 'Phil', __typename: 'Person' },
        { id: 3, name: 'Andy', __typename: 'Person' },
      ],
    };

    const pageTwo = {
      __typename: 'Query',
      persons: [
        { id: 4, name: 'Kadi', __typename: 'Person' },
        { id: 5, name: 'Dom', __typename: 'Person' },
        { id: 6, name: 'Sofia', __typename: 'Person' },
      ],
    };

    write(
      store,
      { query: Pagination, variables: { skip: 3, limit: 3 } },
      pageTwo
    );
    write(
      store,
      { query: Pagination, variables: { skip: 0, limit: 3 } },
      pageOne
    );

    const result = query(store, {
      query: Pagination,
      variables: { skip: 3, limit: 3 },
    });
    expect(result.data).toEqual({
      __typename: 'Query',
      persons: [...pageOne.persons, ...pageTwo.persons],
    });
  });

  it('should preserve the correct order in backward pagination', () => {
    const Pagination = gql`
      query ($skip: Number, $limit: Number) {
        __typename
        persons(skip: $skip, limit: $limit) {
          __typename
          id
          name
        }
      }
    `;

    const store = new Store({
      resolvers: {
        Query: {
          persons: simplePagination({ mergeMode: 'before' }),
        },
      },
    });

    const pageOne = {
      __typename: 'Query',
      persons: [
        { id: 7, name: 'Jovi', __typename: 'Person' },
        { id: 8, name: 'Phil', __typename: 'Person' },
        { id: 9, name: 'Andy', __typename: 'Person' },
      ],
    };

    const pageTwo = {
      __typename: 'Query',
      persons: [
        { id: 4, name: 'Kadi', __typename: 'Person' },
        { id: 5, name: 'Dom', __typename: 'Person' },
        { id: 6, name: 'Sofia', __typename: 'Person' },
      ],
    };

    write(
      store,
      { query: Pagination, variables: { skip: 3, limit: 3 } },
      pageTwo
    );
    write(
      store,
      { query: Pagination, variables: { skip: 0, limit: 3 } },
      pageOne
    );

    const result = query(store, {
      query: Pagination,
      variables: { skip: 3, limit: 3 },
    });

    expect(result.data).toEqual({
      __typename: 'Query',
      persons: [...pageTwo.persons, ...pageOne.persons],
    });
  });

  it('prevents overlapping of pagination on different arguments', () => {
    const Pagination = gql`
      query ($skip: Number, $limit: Number, $filter: string) {
        __typename
        persons(skip: $skip, limit: $limit, filter: $filter) {
          __typename
          id
          name
        }
      }
    `;

    const store = new Store({
      resolvers: {
        Query: {
          persons: simplePagination(),
        },
      },
    });

    const page = withId => ({
      __typename: 'Query',
      persons: [{ id: withId, name: withId, __typename: 'Person' }],
    });

    write(
      store,
      { query: Pagination, variables: { filter: 'one', skip: 0, limit: 1 } },
      page('one')
    );

    write(
      store,
      { query: Pagination, variables: { filter: 'two', skip: 1, limit: 1 } },
      page('two')
    );

    const resOne = query(store, {
      query: Pagination,
      variables: { filter: 'one', skip: 0, limit: 1 },
    });
    const resTwo = query(store, {
      query: Pagination,
      variables: { filter: 'two', skip: 1, limit: 1 },
    });
    const resThree = query(store, {
      query: Pagination,
      variables: { filter: 'three', skip: 2, limit: 1 },
    });

    expect(resOne.data).toHaveProperty('persons[0].id', 'one');
    expect(resOne.data).toHaveProperty('persons.length', 1);

    expect(resTwo.data).toHaveProperty('persons[0].id', 'two');
    expect(resTwo.data).toHaveProperty('persons.length', 1);

    expect(resThree.data).toEqual(null);
  });
});

describe('as directive', () => {
  it('works with forward pagination', () => {
    const Pagination = gql`
      query ($skip: Number, $limit: Number) {
        __typename
        persons(skip: $skip, limit: $limit) @_simplePagination {
          __typename
          id
          name
        }
      }
    `;

    const store = new Store({
      directives: {
        simplePagination: () => simplePagination(),
      },
    });

    const pageOne = {
      __typename: 'Query',
      persons: [
        { id: 1, name: 'Jovi', __typename: 'Person' },
        { id: 2, name: 'Phil', __typename: 'Person' },
        { id: 3, name: 'Andy', __typename: 'Person' },
      ],
    };

    const pageTwo = {
      __typename: 'Query',
      persons: [
        { id: 4, name: 'Kadi', __typename: 'Person' },
        { id: 5, name: 'Dom', __typename: 'Person' },
        { id: 6, name: 'Sofia', __typename: 'Person' },
      ],
    };

    write(
      store,
      { query: Pagination, variables: { skip: 0, limit: 3 } },
      pageOne
    );
    const pageOneResult = query(store, {
      query: Pagination,
      variables: { skip: 0, limit: 3 },
    });
    expect(pageOneResult.data).toEqual(pageOne);

    write(
      store,
      { query: Pagination, variables: { skip: 3, limit: 3 } },
      pageTwo
    );

    const pageTwoResult = query(store, {
      query: Pagination,
      variables: { skip: 3, limit: 3 },
    });

    expect((pageTwoResult.data as any).persons).toEqual([
      ...pageOne.persons,
      ...pageTwo.persons,
    ]);

    const pageThreeResult = query(store, {
      query: Pagination,
      variables: { skip: 6, limit: 3 },
    });
    expect(pageThreeResult.data).toEqual(null);
  });

  it('works with backwards pagination', () => {
    const Pagination = gql`
      query ($skip: Number, $limit: Number) {
        __typename
        persons(skip: $skip, limit: $limit)
          @_simplePagination(mergeMode: "before") {
          __typename
          id
          name
        }
      }
    `;

    const store = new Store({
      directives: {
        simplePagination: directiveArguments =>
          simplePagination({
            mergeMode: directiveArguments!.mergeMode as MergeMode,
          }),
      },
    });

    const pageOne = {
      __typename: 'Query',
      persons: [
        { id: 7, name: 'Jovi', __typename: 'Person' },
        { id: 8, name: 'Phil', __typename: 'Person' },
        { id: 9, name: 'Andy', __typename: 'Person' },
      ],
    };

    const pageTwo = {
      __typename: 'Query',
      persons: [
        { id: 4, name: 'Kadi', __typename: 'Person' },
        { id: 5, name: 'Dom', __typename: 'Person' },
        { id: 6, name: 'Sofia', __typename: 'Person' },
      ],
    };

    write(
      store,
      { query: Pagination, variables: { skip: 0, limit: 3 } },
      pageOne
    );
    const pageOneResult = query(store, {
      query: Pagination,
      variables: { skip: 0, limit: 3 },
    });
    expect(pageOneResult.data).toEqual(pageOne);

    write(
      store,
      { query: Pagination, variables: { skip: 3, limit: 3 } },
      pageTwo
    );

    const pageTwoResult = query(store, {
      query: Pagination,
      variables: { skip: 3, limit: 3 },
    });
    expect((pageTwoResult.data as any).persons).toEqual([
      ...pageTwo.persons,
      ...pageOne.persons,
    ]);

    const pageThreeResult = query(store, {
      query: Pagination,
      variables: { skip: 6, limit: 3 },
    });
    expect(pageThreeResult.data).toEqual(null);
  });
});
